Repository navigation
Find stored values such as f[1] = a through an index, not by trying every rule - #2013
Conversation
…very rule Evaluating f[x] tried the down- (or sub-) values of f one by one, so with n stored values every lookup took time proportional to n: about 8 ms with 8000 values, and code that memoizes many values became quadratic. A rule list now gets an index of its literal rules (unconditional rules whose left-hand side is a literal expression made of symbols, integers and strings, under a head that is not Orderless, Flat or OneIdentity) by the structure of the left-hand side. Only the literal rule SameQ to the expression, if any, is tried, at its place among the other rules, which are tried in order as before. - The place comes from the sort keys by which insert_rule keeps the list ordered. A list not in that order (DownValues set as a whole), or where a literal rule shares its sort key with another rule, is not indexed. - The index belongs to the list object it was built for: a list replaced (Clear, DownValues[f] = ...) gets a new one when needed. Inserting a literal rule updates it; removing a rule or inserting another kind of rule drops it. - Lists of fewer than 8 rules are scanned as before. A lookup in a definition of 1000 stored values now takes about 10 us instead of 1.5 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@tombouctou if we'd fix those merge conflicts, I would like to get this fix in. I looked at the conflicts, but resolving these is not something that is easily done using GitHub's web interface. |
…okup # Conflicts: # mathics/core/definitions.py # test/core/test_definitions.py
|
While this PR is good, as noted elsewhere and in other issues or PRs, the code has a problem with how it handles the order of definitions. So the top-level question is whether we merge this and iterate, or extend this here to handle that additional situation. The basic problem is in literal_keys.add(key)Duplicate assignments get lost. An example of this: f[1] = a
f[2] = b
f[x_] := somethingThese get the same literal index, right? But WMA needs to know to try rule matching in the order given by the assignments. |
…ral rules Rename literal_keys to literal_precedences and explain that many literal rules share one sort key, which is only checked against the keys of the other rules. Test stored values assigned both before and after a pattern rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for looking closely at this. On That set doesn't hold the rules. It holds their sort keys ( For your example, I added both as tests, renamed the set to There is an assignment-order bug, but it is on master and the index doesn't touch it. This PR gives the same output as master here. If the tie-break is changed, the index stays correct: it never decides an order between two non-literal rules, and a literal rule that ties with a non-literal one turns the index off. So I'd suggest merging this and fixing the tie-break separately, either as a small follow-up PR from me or as part of #1989 if @mmatera is already changing that code. |
Ok. LGTM. Thanks for the follow-up. @mmatera: your thoughts? |
…eplaceAll, ReplaceRepeated) (#2019) #2018 is merged; this PR is now the single compiled-rule commit on top of master. `create_rules` made a new `RewriteRule` for every rule on every call of `Replace`, `ReplaceAll`, `ReplaceRepeated` and `ReplaceList`. That means the pattern was compiled again each time. For a list of rules kept in a variable and used again and again (`expr //. rules`), compiling the patterns took longer than matching them. With this PR, the `RewriteRule` is kept on the rule expression together with the value of `definitions.now` at the time it was made. It is reused as long as no symbol of the rule has changed since then, according to `Definitions.is_uncertain_final_value` over the symbols of the rule. A change to attributes, defaults or options marks a symbol as changed, so for example `SetAttributes[h, Orderless]` after the first use causes the rule to be compiled again. For the reuse to happen, the rule must be the same object from one use to the next; #2018 makes that so for rules stored in a list. **Effect:** - Ten `x //. rules` with 750 Flat/Orderless rules made with `Join` take 0.46 s instead of 2.26 s with #2018 alone. - In Strickland's Toda package, where one `x //. AllRelations` uses 962 rules, typical checks become a further 1.5–2 times faster: the check that took 104 s on master takes 10.7 s with #2018 and 5.8 s with both. **Checks:** `test_compiled_rules_are_reused_and_renewed` in `test/builtin/patterns/test_rules.py`. Full pytest and doctests pass. @mmatera, as on #2013: if this overlaps with your rework of rule dispatch, I'm glad to adapt it or to close it in favour of yours. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: R. Bernstein <rocky@users.noreply.github.com>
Evaluating
f[x]tries the down- (or sub-) values offone by one. With n stored values (f[1] = a,f[2] = b, …), every lookup therefore takes time proportional to n: about 8 ms with 8000 values. Code that memoizes many values becomes quadratic.This PR gives a rule list an index of its literal rules, keyed by the structure of the left-hand side. A literal rule is an unconditional rule whose left-hand side is made of symbols, integers and strings, under a head that is not
Orderless,FlatorOneIdentity. Only the literal rule that isSameQto the expression, if there is one, is tried. It is tried at its own place among the other rules, which are still tried in order as before, so rule precedence does not change.insert_rulekeeps the list ordered. A list that is not in that order (for example, afterDownValues[f] = {...}), or in which a literal rule shares its sort key with another rule, is not indexed.ClearorDownValues[f] = ...) gets a new index when one is needed. Inserting a literal rule updates the index; removing a rule, or inserting any other kind of rule, drops it.With 1000 stored values,
f[500]now takes about 10 µs instead of 1.5 ms.Checks:
test_stored_values_lookupintest/core/test_definitions.py.Context: we found this while running the lambda-algebra homology computation from the Toda-bracket project on Mathics3 (see #2001). That code stores tens of thousands of values like
BasisFunction[W][elem] = row. On one core, the change makes loading the definitions about 10% faster, and the gain grows as values accumulate.@mmatera, you mentioned on #2001 that you are reworking rule ordering and dispatch. If this overlaps with that work, I'm glad to rebase it onto your branch, or to close it in favour of yours.
🤖 Generated with Claude Code